﻿using System;
using System.Collections.Generic;
using System.Device.Location;
using Be;
using Dalc;

namespace Bll {

    /// <summary>
    /// The BllBooking class contains all the booking logic
    /// </summary>
    public class BllBooking {

        /// <summary>
        /// The object of the DalcBooking class
        /// </summary>
        private readonly DalcBooking _booking;

        /// <summary>
        /// The object of the DalcLesson class
        /// </summary>
        private readonly DalcLesson _lesson;

        /// <summary>
        /// The speed limit in Varde
        /// </summary>
        private const double Speed = 40.0;

        /// <summary>
        /// Constructs the object
        /// </summary>
        public BllBooking() {
            _booking = new DalcBooking();
            _lesson = new DalcLesson();
        }

        /// <summary>
        /// Get all bookings
        /// </summary>
        /// <returns>All bookings</returns>
        public List<BeBooking> GetBookings() {
            try {
                return _booking.GetBookings();
            } catch(Exception) {
                throw new Exception(BllStatusMessage.ExceptionMessage());
            }
        }

        /// <summary>
        /// Get a booking from an id
        /// </summary>
        /// <param name="id">The id to get the booking from</param>
        /// <returns>The booking</returns>
        public BeBooking GetBookingFromId(int id) {
            try {
                return _booking.GetBookingFromId(id);
            } catch(Exception) {
                throw new Exception(BllStatusMessage.ExceptionMessage());
            }
        }

        /// <summary>
        /// Get all bookings on a lesson
        /// </summary>
        /// <param name="lesson">The lesson to get the bookings from</param>
        /// <returns>The bookings</returns>
        public List<BeBooking> GetBookingsOnLesson(BeLesson lesson) {
            try {
                return _booking.GetBookingsOnLesson(lesson);
            } catch(Exception) {
                throw new Exception(BllStatusMessage.ExceptionMessage());
            }
        }

        /// <summary>
        /// Get all bookings in a specific week
        /// </summary>
        /// <param name="starttimes">The start day</param>
        /// <param name="days">The days from starttimes</param>
        /// <returns>The bookings</returns>
        public List<BeBooking> GetBookingsWeek(DateTime starttimes, int days) {
            try {
                return _booking.GetEvents(starttimes, days);
            } catch(Exception) {
                throw new Exception(BllStatusMessage.ExceptionMessage());
            }
        }

        /// <summary>
        /// Get the suggestions from a booking
        /// on a specific day
        /// </summary>
        /// <param name="booking">The booking to get the suggestions from</param>
        /// <param name="day">The day to get the suggestions from</param>
        /// <returns>The suggestions</returns>
        /*public List<BeBooking> GetSuggestions(BeBooking booking, DateTime day) {
            List<BeBooking> suggestions = new List<BeBooking>();
            try {
                if(_lesson.LessonPresents(booking) == null) return suggestions;
                TimeSpan ts = booking.EndTime - booking.StartTime;
                int meters = 3000;
                int minutesRound = 5;
                List<BeBooking> todaysBookings = _booking.GetBookingsOnDate(day);
                int numberOfSuggestions = 0;
                int maxSuggestions = 3;
                for(int i = 0; i < todaysBookings.Count - 1; i++) {
                    if(numberOfSuggestions == maxSuggestions) break;
                    if(GetDistance(todaysBookings[i], booking) > meters) continue;
                    TimeSpan timeSpan = todaysBookings[i + 1].StartTime - todaysBookings[i].EndTime;
                    if(timeSpan < ts) continue;
                    double dist = GetDistance(todaysBookings[i], booking);
                    TimeSpan timeSpan2 = TimeSpan.FromHours(dist / Speed);
                    booking.StartTime = todaysBookings[i].EndTime + timeSpan2;
                    DateTime suggestionStartTime = booking.StartTime.AddMinutes(minutesRound - (booking.StartTime.Minute % minutesRound));
                    DateTime suggestionEndTIme = suggestionStartTime + ts;
                    suggestions.Add(new BeBooking(suggestionStartTime, suggestionEndTIme, booking.StartLatitude, booking.StartLongitude, booking.EndLatitude,
                        booking.EndLongitude, booking.UserId, booking.TeacherId, booking.LessonId));
                    numberOfSuggestions++;
                }
                return suggestions;
            } catch(Exception) {
                throw new Exception(BllStatusMessage.ExceptionMessage());
            }
        }*/

        /// <summary>
        /// Save a booking
        /// </summary>
        /// <param name="booking">The booking to save</param>
        /// <returns>A message</returns>
        public string SaveBooking(BeBooking booking) {
            try {
                if(HasTimeError(booking)) return BllStatusMessage.TimeMessage();
                BeLesson lesson = _lesson.LessonPresents(booking);
                if(lesson == null) return BllStatusMessage.LessonDonotExists();
                booking.LessonId = lesson.Id;
                if(OverlapsExistingBooking(booking)) return BllStatusMessage.OverlapsMessage();
                if(CantMakeIt(booking)) return BllStatusMessage.TimeMessage();
                return _booking.SaveBooking(booking) ? BllStatusMessage.BookingAddedMessage() : BllStatusMessage.BookingNotAddedMessage();
            } catch(Exception) {
                throw new Exception(BllStatusMessage.ExceptionMessage());
            }
        }

        /// <summary>
        /// Edit a booking
        /// </summary>
        /// <param name="booking">The booking to edit</param>
        /// <returns>A message</returns>
        public string EditBooking(BeBooking booking) {
            try {
                if(HasTimeError(booking)) return BllStatusMessage.TimeMessage();
                BeLesson lesson = _lesson.LessonPresents(booking);
                if(lesson == null) return BllStatusMessage.LessonDonotExists();
                booking.LessonId = lesson.Id;
                if(OverlapsExistingBooking(booking)) return BllStatusMessage.OverlapsMessage();
                if(CantMakeIt(booking)) return BllStatusMessage.TimeMessage();
                _booking.EditBooking(booking);
                return BllStatusMessage.BookingEditMessage();
            } catch(Exception) {
                throw new Exception(BllStatusMessage.ExceptionMessage());
            }
        }

        /// <summary>
        /// Delete a booking
        /// </summary>
        /// <param name="booking">The booking to delete</param>
        /// <param name="role">The role of the user</param>
        /// <returns>A message</returns>
        public string DeleteBooking(BeBooking booking, string role) {
            try {
                TimeSpan timecheck = booking.StartTime - DateTime.Now;
                const double mintimeDeletionStudent = 24;
                const double mintimeDeletionInstructor = 10;
                if(role == "kørelære" && timecheck.TotalMinutes >= mintimeDeletionInstructor) {
                    _booking.DeleteBooking(booking);
                    return BllStatusMessage.BookingDeletedByTeacherMessage();
                }
                if(role == "elev" && timecheck.TotalHours >= mintimeDeletionStudent) {
                    _booking.DeleteBooking(booking);
                    return BllStatusMessage.BookingDeletedByStudentMessage();
                }
                return BllStatusMessage.BookingNotDeleted();
            } catch(Exception) {
                throw new Exception(BllStatusMessage.ExceptionMessage());
            }
        }

        /// <summary>
        /// Get the distance from one booking to another
        /// </summary>
        /// <param name="previousBooking">The previous booking</param>
        /// <param name="nextBooking">The next booking</param>
        /// <returns>The distance</returns>
        private double GetDistance(BeBooking previousBooking, BeBooking nextBooking) {
            GeoCoordinate from = new GeoCoordinate(previousBooking.EndLatitude, previousBooking.EndLongitude);
            GeoCoordinate to = new GeoCoordinate(nextBooking.StartLatitude, nextBooking.StartLongitude);
            return from.GetDistanceTo(to);
        }

        /// <summary>
        /// Check if the booking has a time error
        /// </summary>
        /// <param name="booking">The booking to check</param>
        /// <returns>True if the booking has a time error. False otherwise</returns>
        private bool HasTimeError(BeBooking booking) {
            if(booking.StartTime >= booking.EndTime) return true;
            TimeSpan ts = booking.EndTime - booking.StartTime;
            double totalMinutes = ts.TotalMinutes;
            int lessonTime = 45;
            if(totalMinutes % lessonTime != 0) return true;
            return false;
        }

        /// <summary>
        /// Check if the booking overlaps an existing booking
        /// </summary>
        /// <param name="booking">The booking to test</param>
        /// <returns>True if the booking overlaps an existing booking. False otherwise</returns>
        private bool OverlapsExistingBooking(BeBooking booking) {
            return _booking.OverlapsBookings(booking);
        }

        /// <summary>
        /// Check if the teacher can't make it if
        /// a new booking is made
        /// </summary>
        /// <param name="booking">The new booking</param>
        /// <returns>True if he can't make it. False otherwise</returns>
        private bool CantMakeIt(BeBooking booking) {
            BeBooking pb = _booking.GetPreviousBooking(booking);
            BeBooking nb = _booking.GetNextBooking(booking);
            int days = 1;
            DateTime startTime = nb == null ? booking.EndTime.AddDays(days) : nb.StartTime;
            days = -1;
            DateTime endTime = pb == null ? booking.StartTime.AddDays(days) : pb.EndTime;
            TimeSpan timeSpan = startTime - endTime;
            double dist = pb != null ? GetDistance(pb, booking) : 0;
            double dist2 = nb != null ? GetDistance(booking, nb) : 0;
            const int metersPerKilometer = 1000;
            double totalDist = (dist + dist2) / metersPerKilometer;
            TimeSpan bookingLesson = booking.EndTime - booking.StartTime;
            TimeSpan travelTime = TimeSpan.FromHours(totalDist / Speed) + bookingLesson;
            return timeSpan < travelTime;
        }
    }
}